<?php
/*
 * Project		: Adria IRC Bot realised with PHP 5.x
 * File			: ./class_event.php
 * Version		: 0.1
 * Description	: Definition of Event class.
 * 				  This class is used for executing respective parts of codes
 * 				  in dependence on data incoming from IRC server.
 */

// Return values declaration
define('CEvent_Expired',2);
define('CEvent_Load_FileNotOpen', 3);
define('CEvent_Load_PartUnended', 4);
define('CEvent_Load_PartUnended', 5);
define('CEvent_Load_PartEmpty', 6);

/*
 * Class		: Event
 * Attributes	: private $File - Filename (including relative path) of
 * 								  file with attendance of event.
 * 				  private $Trigger - Array of added "triggers"."Triggers"
 * 									 are stored RegExps(Regular Terms) for
 * 									 every one part of event-file.
 * 				  private $TTL - (Time To Live) Contains time information.
 * 								 Time is saved as nuber of seconds from 1.1.1970
 * 								 (this information returns PHP function Time())
 * 								 If $TTL < Time() the Instance is set as Invalid
 * 				  private $Data - Place for keeping data between parts of event-file
 *
 * Methods		: public Execute($Part = 'n/a', &$Result)
 * 				  public IsValid()
 * 				  public ResumeEvent($IRCLine)
 *
 *  			  private Load($Part = 'n/a', &$Result)
 * 				  ...
 */

class Event {

  private $File; 	// filename
  protected $Trigger = Array ();
  protected $TTL; 	// time to live
  protected $Data;
  protected $RunAgain;
  protected $CronTime;
  protected $Arg = Array ();
  //protected static $EvalErrors = Array();

  function __construct($File) {
    $this->File = ereg_replace('/',DIRECTORY_SEPARATOR,$File).'.php';
    $this->TTL = (Time() + 180);
  }

  function __destruct()
  {
    foreach (array_keys(get_object_vars($this)) as $key)
    {
      unset($this->$key);
    }
  }

  public function SetData($Data)
  {
    $this->Data=$Data;
  }

  public function SetCronTime($CronTime)
  {
    $this->CronTime=$CronTime;
  }

  public function GetTTL() {
    return $this->TTL;
  }

  public function SetTTL($TTL) {
    $this->TTL = $TTL;
  }


  public function TriggerCount()
  {
    return count($this->Trigger);
  }

  public function GetFile() {
    return $this->File;
  }

  public function Lang($Line)
  {
    return $Line;
  }

  //

  /*
   * prida spoust, $part je oznaceni casti kodu, ktera se bude poustet, $regexp je regulerni vyraz ktery bude porovnavan s radkem ktery prije se site
   */
  protected function AddTrigger($Part, $RegExp, $OnTimeout = false) {
    static $i = 0;
    while (isset ($this->Trigger[$i])) {
      $i++;
    }
    $this->Trigger[$i] = array (
    'Part' => $Part,
    'RegExp' => $RegExp,
    'OnTimeout' => $OnTimeout
    );
    if ($this->TTL < (Time() + 180)) $this->TTL = (Time() + 180);
  }
  protected function AddTimeOutTrigger($Part)
  {
    $this->AddTrigger($Part, false, true);
  }
  /**
   * zlikviduje vsechny spouste, ktere tento event mohou spustit
   */
  protected function DestroyAllTriggers() {
    $this->Trigger = array ();
  }



  public static function ErrorHandler($ErrNo, $ErrStr,$ErrFile,$ErrLine,$ErrContext,$Flush = false)
  {
    static $Errors = array();
    if ($Flush) {
      $tmp = $Errors;
      $Errors = array();
      return $tmp;
    }
    $Errors[] = array('type' =>  $ErrNo, 'message' => $ErrStr, 'file' => $ErrFile, 'line' => $ErrLine,'context' => $ErrContext);
  }


  protected function SafeEval($Code)
  {
    $Log = '';
    //set_error_handler(array($this, 'ErrorHandler'));
    if (function_exists('error_get_last'))
    {
      if ($unassociated) {};
      $LastError[0] = error_get_last();
    }
    set_error_handler('Event::ErrorHandler');
    eval($Code);
    restore_error_handler();
    $EvalErrors = $this->ErrorHandler('','','','','',true);
    if (function_exists('error_get_last')){
      $LastError[1] = error_get_last();
      if (($LastError[0]['type'] != $LastError[1]['type']) || ($LastError[0]['message'] != $LastError[1]['message']) || ($LastError[0]['file'] != $LastError[1]['file']) || ($LastError[0]['line'] != $LastError[1]['line']))$EvalErrors[] = $LastError[1];
    }
    if (count($EvalErrors) > 0) {
      foreach ($EvalErrors as $Errors)
      {
        $Log .=  'Error';
        if (isset($Errors['line'] )) $Log .= ' on line '.$Errors['line'];
        if (isset($Errors['type'] )) $Log .= ' : '.$Errors['type'];
        if (isset($Errors['message'] )) $Log .= ' '.$Errors['message'];
        if (isset($Errors['file'] )) $Log .= ' in file: '.$Errors['file'];
        if (isset($Errors['context'] )) $Log .= ' in context: '.$Errors['context'];
        $Log .= "\n";
      }
      $Code = (split("\n",$Code));
      $length = strlen(count($Code));
      for ($i = 1;$i <= count($Code);$i++)
      {
        foreach($EvalErrors as $Errors)
        $Log .= ($Errors['line'] == $i)?'!':' ';
        $Log .= sprintf('%'.$length.'d',$i).':'.$Code[$i-1]."\n";
      }
      echo $Log;
      $GLOBALS['Log']->Debug($Log);
      return(false);
    } else {
      return(true);
    }
  }


  /**
   * Function Execute part of Event file.
   *    $Part name of code part in Event file
   *    $Result If function returns false contains error number
   */
  public function Execute($Part = 'n/a', &$Result = NULL)
  {
    if (($Code = $this->Load($Part,$Error)) != FALSE)
    {
      $this->RunAgain = false;
      $this->SafeEval($Code);
      //eval($Code);
      if ($this->RunAgain)
      {
        if (($Code = $this->Load('n/a',$Error)) != FALSE)
        {
          $this->SafeEval($Code);
        }
      }
      return true;
    }
    else
    {  // Event cant be executed becouse of : $Result
      $Result = $Error;
      return false;
    }

  }

  /**
   * Function open Event file and find part idetntified by $Part If code part exists returns content of this part, else returns false  and error number.
   *   $Part string name of code part in Event file
   *   $Result mixed If function returns false contains error number
   */

  private function Load($Part = 'n/a', &$Result) {
    $Code = '';
    $Useable = false;
    $CodeLoaded = false;
    if (($FP_EventFile = fopen($this->File, 'r')) != FALSE)
    {
      while ((!feof($FP_EventFile)) && (!$CodeLoaded))
      {
        $Line = trim(FGetS($FP_EventFile, 4096));
        if  (($Part == 'n/a') && (eregi('^<[ ]*code[ ]*>',$Line))) $Useable = true;
        elseif (($Part != 'n/a') && (eregi('^<[ ]*code[ ]* '.$Part.'[ ]*>',$Line))) $Useable = true;
        elseif (($Useable) && (eregi('^</code>',$Line))) {$Useable = false; $CodeLoaded=true;}
        elseif ($Useable)  $Code .= $Line."\n";
      }
      fclose($FP_EventFile);
      if (!$CodeLoaded)			// Code wasn't sucefully loaded
      {
        $Result=($Useable?CEvent_Load_PartUnended:CEvent_Load_NotExists);
        if ($Useable) $GLOBALS['Log']->Sys('required part has no end in \''.$this->RunningEvent[$EID]->GetFile.'\'');
        else $GLOBALS['Log']->Sys('required part doesn\'t exists in \''.$this->RunningEvent[$EID]->GetFile.'\'');
        return false;
      }
      elseif ($Code == '')
      {
        $GLOBALS['Log']->Sys('required part is empty in \''.$this->RunningEvent[$EID]->GetFile.'\'');
        $Result = CEvent_Load_PartEmpty;
        return false;
      }
      else return $Code;
    }
    else
    {
      // Event file openning crashed
      $GLOBALS['Log']->Sys('event file (\''.$this->RunningEvent[$EID]->GetFile.'\') can\'t be opened');
      $Result = CEvent_Load_FileNotOpen;
      return false;
    }
  }

  public function IsValid(){
    return ($this->TTL > time());
  }

  public function Timer($Time = 'n/a')
  {
    if ($Time === 'n/a') $Time = time();
    if ($this->TTL < $Time){
      foreach ($this->Trigger as &$Exp)
      {

        if ($Exp['OnTimeout'])
        {
          $tmp=$this->Execute($Exp['Part'],$Error);
          break;
        }
      }
      if ($this->TTL < $Time) {
        $Result = CEvent_Expired;
        $GLOBALS['Log']->Sys('Event canceled becouse of timeout');
        return false;
      }
      else return (($tmp==true)?true:$Error);
    }

  }

  /**
   * This function pass all trigers and compare those RegExp(s) with line comes form IRC comunication. If the RegExp matches, execute relevant code.
   * Returns		: true - If all is ok (nothing to execute or executed without problems)
   * 				  CEvent_Expired - if is event expired - instace !!isn't!! deleted
   * 				  CEvent_Load_FileNotOpen - if event file can't be opened
   * 				  CEvent_Load_NotExists - if required part doesn't exists
   * 				  CEvent_Load_PartUnended - if required part has no end
   * 				  CEvent_Load_PartEmpty - if required part is empty
   */
  public function ResumeEvent($IRCLine) {
    foreach ($this->Trigger as &$Exp)
    {
      //echo 'testuji z eventu regexp ['.$Exp['RegExp'].'] na ['.$IRCLine.']';
      if (($Exp['RegExp'] != false) && (ereg($Exp['RegExp'], $IRCLine)))
      {
        $tmp=$this->Execute($Exp['Part'],$Error);
        return (($tmp==true)?true:$Error);
      }
    }
    return true;
  }

  /**
   * Kontroluje jestli neexistuje trigger, se shodným regexpem
   *
   * @param string $RegExp
   * @return bool
   */
  public function InTriggers($RegExp){
    foreach ($this->Trigger as &$Exp)
    {
      if (($Exp['RegExp'] != false) && ($Exp['RegExp'] == $RegExp)) return true;
    }
    return false;
  }
}
?>